-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added support for maximum expiration of a JWT token #3331
Conversation
76ef187
to
987cda5
Compare
I like the idea here, but not all JWT's will include the exp and nbf, why not also make an if validating on if (exp - ngx.time()) or w/e you have if you want to keep token expiry an hour or less? It's something we implemented too to prevent clients from being irresponsible with their tokens. |
I thought it would be cheaper to do the (exp -nbf) calculation. In addition, with (exp-nbf) I did not have to deal with clock skew. I will change it, to use exp only. leaving the configuration of maximum_expiration to deal with possible clock skew.. Cheers, Mark |
@jeremyjpj0916 can you please review my pull request? Once it is integrated, I can stop merging the changes from master into this one 😃 |
@mvanholsteijn I don't have write access, I chimed in only because I was implementing a similar change personally and thought to bring up the scenario where just exp is exposed and to compare against ngx.time() , which is looks like you added that so overall I think this is a nice feature! Hopefully kong team agrees and will consider implementing it as it makes sense from a security perspective. |
@jeremyjpj0916 I did not know you did not have write access :-) We implemented this feature after an external security audit was performed on our API. They recommended to limit the lifetime of the jwt token. |
@thibaultcha Do you have any idea on when this PR might be reviewed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvanholsteijn Thank you for the patch! And sorry for the delay.
Will you apply the recommended changes? Also, since this patch contains migrations, it must be opened against next
, and not master
. Please do not close this PR and open a new one, instead, use the GitHub UI to change the base of your PR (it might need rebasing as well).
Since the next release version is 0.14.0 anyway, there it should not delay this from being released.
local payload = { | ||
iss = jwt_secret.key, | ||
exp = os.time() + 3600, | ||
nbf = os.time() - 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: indentation is off here and in several other places in this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
kong/plugins/jwt/schema.lua
Outdated
end | ||
end | ||
if not has_exp then | ||
return false, Errors.schema "when you specify maximum_expiration, 'exp' must be in claims_to_verify" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: indentation is off here and above in the same function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to come up with a more standard/consistent error message:
"claims_to_verify must contain 'exp' when specifying maximum_expiration"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to "To set maximum_expiration, you need to add 'exp' in claims_to_verify"
path = "/request/?jwt=" .. jwt, | ||
headers = { | ||
["Host"] = "jwt11.com" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto: indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
kong/plugins/jwt/schema.lua
Outdated
@@ -18,5 +26,20 @@ return { | |||
claims_to_verify = {type = "array", enum = {"exp", "nbf"}}, | |||
anonymous = {type = "string", default = "", func = check_user}, | |||
run_on_preflight = {type = "boolean", default = true}, | |||
maximum_expiration = {type = "number", default = -1, func = check_maximum_expiration_positive}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a more common pattern is to treat 0
as the "indefinite expiration", and any value below zero as invalid (as if it were an unsigned integer). The semantics are somewhat more obvious (-1
has a strong "error-related" connotation and also feels strange in a time-related value).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right. fixed.
@@ -165,6 +165,15 @@ local function do_authentication(conf) | |||
return false, {status = 401, message = errors} | |||
end | |||
|
|||
-- Verify the JWT registered claims | |||
if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second check of this condition seems unnecessary, since you included a safeguard to check_maximum_expiration()
already (if max_exp <= 0
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to avoid calling the function as I know it is not necessary saving a few CPU cycles.
kong/plugins/jwt/jwt_parser.lua
Outdated
end | ||
else | ||
return false, {exp = "is missing"} | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about: "exceeds maximum allowed expiration"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, as per our contributing guide, we recommend hoisting short branches with early exits for clarity, and a few other minor nitpicks:
local exp = self.claims.exp
if not exp then
return false, { exp = "missing" }
end
if exp - ngx_time() > maximum_expiration then
return false, { exp = "exceeds maximum allowed expiration" }
end
return true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to the message "exceeds maximum allowed expiration" if the 'exp' is missing.
kong/plugins/jwt/schema.lua
Outdated
@@ -8,6 +9,13 @@ local function check_user(anonymous) | |||
return false, "the anonymous user must be empty or a valid uuid" | |||
end | |||
|
|||
local function check_maximum_expiration_positive(v) | |||
if v < -1 or v == 0 then | |||
return false, "maximum_expiration should be -1 or greater than 0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to repeat the name of the property in this error message, it will already be contained in the key for this error. We are also lacking a test for this validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
kong/plugins/jwt/schema.lua
Outdated
local has_exp = false | ||
if plugin_t.claims_to_verify then | ||
for index, value in ipairs(plugin_t.claims_to_verify) do | ||
has_exp = has_exp or value == "exp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more convenient pattern is to break out of the loop:
if value == "exp" then
has_exp = true
break
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@thibaultcha I processed all (but 1) remarks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @mvanholsteijn
I think this is almost ready. I was about to merge your patch manually with the modifications I just mentioned, but since there is a merge commit in your PR, it made things more difficult and I'd rather ask you to either properly rebase your branch on top of next, or made those modifications yourself (in which case I will squash + merge your changes). A proper rebase would be much appreciated as it makes our life a lot easier. For reference see the CONTRIBUTING.md guidelines which recommend following this process when contributing to this repo.
Thank you!
kong/plugins/jwt/schema.lua
Outdated
end | ||
end | ||
if not has_exp then | ||
return false, Errors.schema "To set maximum_expiration, you need to add 'exp' in claims_to_verify" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A convention followed by absolutely all error messages in Kong is to not capitalize the sentences. Better not deviate from it (hence my previous suggestion).
kong/plugins/jwt/schema.lua
Outdated
@@ -8,6 +9,13 @@ local function check_user(anonymous) | |||
return false, "the anonymous user must be empty or a valid uuid" | |||
end | |||
|
|||
local function check_positive(v) | |||
if v < 0 then | |||
return false, "should be >= 0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another convention our error messages follow is to use words instead of signs. In this case:
should be 0 or greater
or:
cannot be negative
kong/plugins/jwt/jwt_parser.lua
Outdated
|
||
local exp = self.claims["exp"] | ||
if exp == nil or (exp - ngx_time()) > maximum_expiration then | ||
return false, {exp = "exceeds maximum allowed expiration"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: the indentation in this codebase is 2 spaces (this one uses 4 and the above branch uses 3)
kong/plugins/jwt/jwt_parser.lua
Outdated
-- @return error if any | ||
function _M:check_maximum_expiration(maximum_expiration) | ||
if maximum_expiration <= 0 then | ||
return true, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return true
would be enough here as per our return conventions.
kong/plugins/jwt/schema.lua
Outdated
if plugin_t.maximum_expiration ~= nil and plugin_t.maximum_expiration > 0 then | ||
local has_exp = false | ||
if plugin_t.claims_to_verify then | ||
for index, value in ipairs(plugin_t.claims_to_verify) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: indentation is weird here as well (1 space?)
44190ec
to
20a8b3a
Compare
Fixed latest review comments and rebased on upstream/next. PS: Is there a way to get luacheck to complain about indentation? |
20a8b3a
to
92a9b7c
Compare
Support for limiting the expiration period on JWT tokens. In the JWT plugin you can set the property `maximum_expiration` to a positive integer, indicating the maximum number of seconds the `exp` claim in the token may be ahead in the future. From #3331 Signed-off-by: Thibault Charbonnier <[email protected]>
Merged with some modifications (testing, formatting, errors), thanks again for your contribution |
@thibaultcha thank you for your support! |
Summary
Support for limiting the expiration period on JWT tokens.
In the JWT plugin you can set the property 'maximum_expiration' to a positive integer, indicating the maximum number of seconds the 'exp' claim in the token may be ahead in the future.
The purpose of this feature is to prevent the use of tokens with extreme long lifetimes.
Full changelog